package cn.edu.lcu.cs.javaprogramming.gui.notepad;

import java.awt.BorderLayout;
import java.awt.EventQueue;

import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import javax.swing.filechooser.FileFilter;
import javax.swing.text.BadLocationException;
import javax.swing.JMenuBar;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.KeyStroke;
import java.awt.event.KeyEvent;
import java.awt.event.InputEvent;
import javax.swing.JSeparator;
import javax.swing.JTextArea;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JLabel;
import java.awt.event.MouseMotionAdapter;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.awt.event.MouseEvent;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.event.KeyAdapter;
import javax.swing.event.CaretListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.CaretEvent;
import javax.swing.JToolBar;
import javax.swing.JButton;

/**
 * Eclipse WindowBuilder插件设计的记事本程序
 */
public class NotepadClipse extends JFrame {

    /**
     * 支持的文件类型扩展名
     */
    private final String[] exFileNames = {".txt", ".java", ".c", ".properties", ".md"};
    private final String APP_NAME = "我的记事本";
    private final String DEFAULT_FILE_NAME = "文档";
    /**
     * 当前正在编辑的文件。<br>
     * 如果为空，说明当前编辑内容从未保存过，应该打开另存为对话框，或者按照默认名称保存文件
     */
    private File currentFile;
    /**
     * 脏标记，表明内存文件缓冲是否与外存磁盘文件内容一致
     */
    private boolean dirty;
    private JPanel contentPane;
    /**
     * 显示字数
     */
    private JTextField wordCountField;
    /**
     * 显示鼠标位置
     */
    private JTextField mouseField;
    /**
     * 文本区，编辑区
     */
    private JTextArea textArea;
    /**
     * 显示插入点位置
     */
    private JLabel caretLabel;

    /**
     * Create the frame.
     */
    public NotepadClipse() {
        setTitle(APP_NAME);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setBounds(100, 100, 450, 300);

        contentPane = new JPanel();
        contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
        contentPane.setLayout(new BorderLayout(0, 0));
        // 设置内容窗格
        setContentPane(contentPane);

        JScrollPane scrollPane = new JScrollPane();
        contentPane.add(scrollPane, BorderLayout.CENTER);
        // 设置工具栏
        scrollPane.setColumnHeaderView(initToolBar());
        // 设置菜单栏
        initMenuBar();
        // 设置状态栏
        initStatusBar();

        // 主文本区，编辑区
        textArea = new JTextArea();

        // Document事件：当文档内容变量时触发
        textArea.getDocument().addDocumentListener(new DocumentListener() {

            @Override
            public void removeUpdate(DocumentEvent e) {
                // 文档一部分内容被删除了，就是文档内容变更了
                documentChange();
            }

            @Override
            public void insertUpdate(DocumentEvent e) {
                // 内容删除
                documentChange();
            }

            @Override
            public void changedUpdate(DocumentEvent e) {
                // 属性变更
                documentChange();
            }

            private void documentChange() {
                dirty = true;
                updateTitle();
            }
        });

        // 插入点事件
        textArea.addCaretListener(new CaretListener() {
            public void caretUpdate(CaretEvent e) {
                // 插入点位置更新时触发
                // 插入点与文件首的偏移
                int position = textArea.getCaretPosition();
                try {
                    // 获取插入点行号，起始于0
                    int line = textArea.getLineOfOffset(position);
                    // 行首在整个文档中的偏移
                    int lineStartOffset = textArea.getLineStartOffset(line);
                    // 插入点在当前行中的偏移，起始于0
                    int col = position - lineStartOffset;
                    caretLabel.setText((line + 1) + ":" + col);
                } catch (BadLocationException e1) {
                    e1.printStackTrace();
                }

            }
        });

        // 添加键盘事件监听器，本例中其实没有用到此功能。
        // 键盘事件的级别比较低，不能完整监听文档内容的变化，应该监听高级别的文档事件。
        textArea.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                // 键盘按下事件，慢一拍，而且对输入法无视
//				wordCountField.setText("字数：" + textArea.getText().length());
            }

            @Override
            public void keyTyped(KeyEvent e) {
                // 对输入法敏感，但是慢一拍
//				wordCountField.setText("字数：" + textArea.getText().length());
            }

            @Override
            public void keyReleased(KeyEvent e) {
                // 按键释放事件，对输入法敏感
                // todo 监控文本组件的内容变化，不能用键盘事件 ???
//				wordCountField.setText("字数：" + textArea.getText().length());
            }
        });
        textArea.addMouseMotionListener(new MouseMotionAdapter() {
            @Override
            public void mouseMoved(MouseEvent e) {
                // 获取mouseField, 更新它显示的文本
                String content = String.format("鼠标（%d,%d）", e.getX(), e.getY());
                mouseField.setText(content);
            }
        });
        scrollPane.setViewportView(textArea);


    }

    private JToolBar initToolBar() {
        JToolBar toolBar = new JToolBar();

        JButton fileNewBtn = new JButton("新建");
        toolBar.add(fileNewBtn);

        JButton fileOpenBtn = new JButton("打开");
        fileOpenBtn.addActionListener(new FileOpenActionListener());
        //fileOpenBtn.setIcon(new ImageIcon(MyNotepad.class.getResource("/com/sun/java/swing/plaf/windows/icons/File.gif")));
        toolBar.add(fileOpenBtn);

        JButton fileSaveBtn = new JButton("保存");
        fileSaveBtn.addActionListener(new FileSaveActionListener());
        toolBar.add(fileSaveBtn);

        return toolBar;
    }

    private void initStatusBar() {
        // 状态栏
        JPanel statusBar = new JPanel();
        contentPane.add(statusBar, BorderLayout.SOUTH);

        mouseField = new JTextField();
        mouseField.setText("鼠标（0,0）");
        mouseField.setBorder(null);
        mouseField.setEditable(false);
        statusBar.add(mouseField);
        mouseField.setColumns(15);

        wordCountField = new JTextField();
        wordCountField.setText("字数：0");
        wordCountField.setBorder(null);
        wordCountField.setEditable(false);
        statusBar.add(wordCountField);
        wordCountField.setColumns(10);

        caretLabel = new JLabel("0：0");
        statusBar.add(caretLabel);
    }

    /**
     * 初始化菜单栏
     */
    private void initMenuBar() {
        // 创建菜单栏
        JMenuBar menuBar = new JMenuBar();
        // 为JFrame设置菜单栏
        setJMenuBar(menuBar);

        // 创建主菜单--文件菜单
        JMenu fileMenu = new JMenu("文件（F）");
        // 为菜单设置助词符，即 Alt+Key 组合键
        //fileMenu.setMnemonic('F');
        fileMenu.setMnemonic(KeyEvent.VK_F);
        // 将主菜单添加到菜单栏
        menuBar.add(fileMenu);

        // 创建菜单项--文件--新建
        JMenuItem fileNewMenuItem = new JMenuItem("新建");
        // 为菜单项设置加速键（功能键）
        fileNewMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, InputEvent.CTRL_DOWN_MASK));
        // 将菜单项添加到主菜单
        fileMenu.add(fileNewMenuItem);

        JMenuItem fileOpenMenuItem = new JMenuItem("打开");
        // 为菜单项添加事件监听器-动作事件--打开文件
        fileOpenMenuItem.addActionListener(new FileOpenActionListener());
        fileOpenMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, InputEvent.CTRL_DOWN_MASK));
        fileMenu.add(fileOpenMenuItem);

        JMenuItem fileSaveMenuItem = new JMenuItem("保存");
        fileSaveMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK));
        fileSaveMenuItem.addActionListener(new FileSaveActionListener());
        fileMenu.add(fileSaveMenuItem);

        // 在主菜单中添加分隔线
        JSeparator separator = new JSeparator();
        fileMenu.add(separator);

        JMenuItem fileExitMenuItem = new JMenuItem("退出");
        fileExitMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X, InputEvent.ALT_DOWN_MASK));
        // 释放当前窗口的屏幕资源，但不一定会导致程序退出
        //fileExitMenuItem.addActionListener(e -> dispose());
        // 释放所有窗口资源，退出程序，返回OS
        fileExitMenuItem.addActionListener(e -> System.exit(0));
        fileMenu.add(fileExitMenuItem);

        JMenu editMenu = new JMenu("编辑（E）");
        editMenu.setMnemonic('E');
        menuBar.add(editMenu);

        JMenu helpMenu = new JMenu("帮助（H）");
        helpMenu.setMnemonic('H');
        menuBar.add(helpMenu);
    }

    /**
     * 更新窗口标题
     */
    private void updateTitle() {
        wordCountField.setText("字数 " + textArea.getText().length());
        setTitle((dirty ? "*" : "") + APP_NAME + " - " + (currentFile == null ? DEFAULT_FILE_NAME : currentFile.getName()));
    }

    /**
     * Launch the application.
     */
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    NotepadClipse frame = new NotepadClipse();
                    frame.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * 打开文件的动作事件监听器类。 <br>
     * 当点击相应菜单项或功能按钮的时候激活。
     */
    private final class FileOpenActionListener implements ActionListener {
        public void actionPerformed(ActionEvent e) {
            // 显示文件打开对话框，选择文本文件并打开，将文件内容读取到textarea

            // 创建文件选择器
            JFileChooser fileChooser = new JFileChooser(".");

            // 让文件选择器只选择指定类型的文件
            fileChooser.setFileFilter(new FileFilter() {

                @Override
                public String getDescription() {
//						return "文本文件（.txt）";
                    return Arrays.toString(exFileNames);
                }

                @Override
                public boolean accept(File f) {
                    if (f.isDirectory()) {
                        return true;
                    }
//						return f.getName().toLowerCase().endsWith(".txt");
                    String lowerCaseFileName = f.getName().toLowerCase();
                    return Arrays.stream(exFileNames).anyMatch(exFileName -> {
                        return lowerCaseFileName.endsWith(exFileName);
                    });
                }
            });

            fileChooser.showOpenDialog(null); // 显示文件打开对话框

            currentFile = fileChooser.getSelectedFile();
            if (currentFile != null) {
                // 读取文件所有内容，更新到textarea里
                try {
                    byte[] bytes = Files.readAllBytes(currentFile.toPath());
                    String content = new String(bytes);
                    textArea.setText(content);
                    wordCountField.setText("字数：" + content.length());
                    dirty = false;
                    updateTitle();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }

        }
    }

    /**
     * 文件保存菜单项或工具按钮的事件监听器
     */
    private final class FileSaveActionListener implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {
            if (dirty) {
                // 如果当前打开文件非空，直接写文件
                if (currentFile != null) {
                    try {
                        Files.write(currentFile.toPath(), textArea.getText().getBytes(StandardCharsets.UTF_8));
                    } catch (IOException ex) {
                        throw new RuntimeException(ex);
                    }
                }

                dirty = false;
                updateTitle();
            }
        }
    }
}
